import logging
import os
import time
from abc import ABC, abstractmethod
from pathlib import Path
from time import sleep
from tkinter import Tk, Label, Entry, Button, messagebox, StringVar, Frame
from tkinter.ttk import Notebook

from selenium.common.exceptions import TimeoutException
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service

from extract_file import extract_and_copy, setup_logging, extract
from tool import get_filname, is_sn_valid

# 配置信息
LOGIN_URL = "https://iot-admin.zuoyebang.cc/iot-admin-server/index"
USERNAME = "zhongweiquan"
PASSWORD = "12345678@Zwq"
PHONE = "19200595149"
chromedriver_path = r"D:\实习生工作\chromedriver-win32\chromedriver.exe"


# ====================== 观察者模式核心 ======================
class DownloadState:
    """下载状态枚举"""
    IDLE = "空闲"
    AUTO_DOWNLOADING = "自动下载中"
    MANUAL_REQUIRED = "需要手动下载"
    COMPLETED = "完成"
    FAILED = "失败"


class DownloadSubject:
    """被观察的下载上下文"""

    def __init__(self):
        self._observers = []
        self._state = DownloadState.IDLE
        self._strategy = None

    def attach(self, observer):
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer):
        self._observers.remove(observer)

    def notify(self):
        for observer in self._observers:
            observer.update(self)

    @property
    def state(self):
        return self._state

    @state.setter
    def state(self, value):
        self._state = value
        self.notify()

    @property
    def strategy(self):
        return self._strategy

    @strategy.setter
    def strategy(self, value):
        self._strategy = value


class DownloadObserver(ABC):
    """观察者基类"""

    @abstractmethod
    def update(self, subject):
        pass


class StrategySwitchObserver(DownloadObserver):
    """策略切换观察者"""

    def update(self, subject):
        if subject.state == DownloadState.MANUAL_REQUIRED:
            logging.info(" 检测到需要手动下载，正在切换策略...")
            subject.strategy = self._create_manual_strategy()

    def _create_manual_strategy(self):
        """创建带装饰器的手动策略"""
        base_strategy = ManualDownloadStrategy()
        return LoggingDownloadDecorator(
            RetryDownloadDecorator(base_strategy, max_retries=1)
        )


# ====================== 策略模式 ======================
class DownloadStrategy(ABC):
    """下载策略接口"""

    @abstractmethod
    def download(self, link, expected_filename, base_dir):
        pass


class AutoDownloadStrategy(DownloadStrategy):
    """自动下载策略"""

    def download(self, link, expected_filename, base_dir):
        try:
            link.click()
            return self._wait_for_file(expected_filename, base_dir)
        except Exception as e:
            logging.warning(f" 自动下载失败: {str(e)}")
            raise

    def _wait_for_file(self, filename, base_dir, timeout=300):
        start_time = time.time()
        while True:
            if filename in os.listdir(base_dir):
                return base_dir / filename
            if time.time() - start_time > timeout:
                raise TimeoutError("自动下载超时")
            sleep(1)


class ManualDownloadStrategy(DownloadStrategy):
    """手动下载策略"""

    def download(self, link, expected_filename, base_dir):
        messagebox.showinfo(" 提示", "请手动下载文件")
        return self._wait_for_file(expected_filename, base_dir)

    def _wait_for_file(self, filename, base_dir, timeout=300):
        start_time = time.time()
        existing_files = set(os.listdir(base_dir))
        while True:
            current_files = set(os.listdir(base_dir))
            new_files = current_files - existing_files
            for f in new_files:
                if filename in f:
                    return base_dir / f
            if time.time() - start_time > timeout:
                raise TimeoutError("手动下载等待超时")
            sleep(1)


class SmartDownloadStrategy(DownloadStrategy):
    """智能下载策略（自动切换手动）"""

    def __init__(self):
        self.auto_strategy = self._create_auto_strategy()
        self.manual_strategy = None

    def _create_auto_strategy(self):
        """创建带装饰器的自动策略"""
        base_strategy = AutoDownloadStrategy()
        return LoggingDownloadDecorator(
            RetryDownloadDecorator(base_strategy, max_retries=3)
        )

    def download(self, link, expected_filename, base_dir):
        try:
            # 先尝试自动下载
            return self.auto_strategy.download(link, expected_filename, base_dir)
        except Exception as e:
            logging.warning(f" 自动下载失败，准备切换手动策略: {str(e)}")
            if not self.manual_strategy:
                self.manual_strategy = ManualDownloadStrategy()
            return self.manual_strategy.download(link, expected_filename, base_dir)


# ====================== 装饰器模式 ======================
class DownloaderDecorator:
    """下载装饰器基类"""

    def __init__(self, downloader):
        self._downloader = downloader

    def download(self, *args, **kwargs):
        return self._downloader.download(*args, **kwargs)


class RetryDownloadDecorator(DownloaderDecorator):
    """重试装饰器"""

    def __init__(self, downloader, max_retries=3):
        super().__init__(downloader)
        self.max_retries = max_retries

    def download(self, *args, **kwargs):
        for attempt in range(self.max_retries):
            try:
                return super().download(*args, **kwargs)
            except Exception as e:
                if attempt == self.max_retries - 1:
                    raise
                logging.warning(f" 下载失败，正在重试... ({attempt + 1}/{self.max_retries})")
                sleep(2)


class LoggingDownloadDecorator(DownloaderDecorator):
    """日志记录装饰器"""

    def download(self, *args, **kwargs):
        logging.info(" 开始下载文件...")
        try:
            result = super().download(*args, **kwargs)
            logging.info(" 文件下载成功")
            return result
        except Exception as e:
            logging.error(f" 下载失败: {str(e)}")
            raise


# ====================== 工厂方法模式 ======================
class BrowserFactory(ABC):
    """浏览器工厂基类"""

    @abstractmethod
    def create_driver(self):
        pass


class ChromeFactory(BrowserFactory):
    """Chrome浏览器工厂"""

    def create_driver(self):
        chrome_options = webdriver.ChromeOptions()
        prefs = {
            "download.default_directory": r"D:\实习生工作",
            "download.prompt_for_download": False,
            "download.directory_upgrade": True,
            "safebrowsing.enabled": True,
        }
        chrome_options.add_experimental_option("prefs", prefs)
        chrome_options.add_experimental_option("detach", True)
        return webdriver.Chrome(
            service=Service(chromedriver_path),
            options=chrome_options
        )


# ====================== 页面观察者 ======================
class PageDownloadObserver:
    """页面元素观察者"""

    def __init__(self, driver, poll_interval=1, timeout=300):
        self.driver = driver
        self.poll_interval = poll_interval
        self.timeout = timeout
        self.search_button_xpath = '//*[@id="form-search"]/div/ul/li[2]/a[1]'
        self.download_link_xpath = '//*[@id="bootstrap-table"]/tbody/tr/td[6]/a'

    def wait_for_download_links(self):
        start_time = time.time()
        while True:
            try:
                WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.XPATH, self.search_button_xpath))
                ).click()
            except:
                pass

            try:
                links = WebDriverWait(self.driver, 1).until(
                    EC.presence_of_all_elements_located((By.XPATH, self.download_link_xpath))
                )
                return links
            except TimeoutException:
                pass

            if time.time() - start_time > self.timeout:
                raise TimeoutException(f"等待下载链接超时({self.timeout} 秒)")
            time.sleep(self.poll_interval)

        # ====================== 主应用类 ======================


class LoginApp:
    def __init__(self, master):
        self.master = master
        master.title(" 设备日志下载工具")
        self.driver = None
        self.is_logged_in = False
        self._setup_ui()
        self.download_context = self._create_download_context()

    def _create_download_context(self):
        """创建下载上下文并配置观察者"""
        context = DownloadSubject()
        context.attach(StrategySwitchObserver())
        context.strategy = SmartDownloadStrategy()
        return context

    def _setup_ui(self):
        """初始化UI界面"""
        self.notebook = Notebook(self.master)
        self.notebook.pack(pady=10, padx=10, fill='both', expand=True)

        # 登录设置标签页
        login_frame = Frame(self.notebook)
        Label(login_frame, text="用户名:").grid(row=0, column=0, padx=10, pady=5, sticky='e')
        self.username_entry = Entry(login_frame)
        self.username_entry.grid(row=0, column=1, padx=10, pady=5)
        self.username_entry.insert(0, USERNAME)

        Label(login_frame, text="密码:").grid(row=1, column=0, padx=10, pady=5, sticky='e')
        self.password_entry = Entry(login_frame, show="*")
        self.password_entry.grid(row=1, column=1, padx=10, pady=5)
        self.password_entry.insert(0, PASSWORD)

        Label(login_frame, text="手机号:").grid(row=2, column=0, padx=10, pady=5, sticky='e')
        self.phone_entry = Entry(login_frame)
        self.phone_entry.grid(row=2, column=1, padx=10, pady=5)
        self.phone_entry.insert(0, PHONE)

        # 设备设置标签页
        sn_frame = Frame(self.notebook)
        Label(sn_frame, text="设备SN:").grid(row=0, column=0, padx=10, pady=5, sticky='e')
        self.sn_entry = Entry(sn_frame)
        self.sn_entry.grid(row=0, column=1, padx=10, pady=5)

        self.notebook.add(login_frame, text="登录设置")
        self.notebook.add(sn_frame, text="设备设置")

        # 按钮区域
        button_frame = Frame(self.master)
        button_frame.pack(pady=10)
        Button(button_frame, text="初始化浏览器", command=self.initialize_browser).pack(side='left', padx=5)
        self.start_button = Button(button_frame, text="开始下载", command=self.start_process, state='disabled')
        self.start_button.pack(side='left', padx=5)

        # 状态栏
        self.status_var = StringVar()
        Label(self.master, textvariable=self.status_var).pack(pady=5)

    def initialize_browser(self):
        """初始化浏览器"""
        try:
            self.status_var.set(" 正在初始化浏览器...")
            self.master.update()

            # 使用工厂方法创建浏览器实例
            factory = ChromeFactory()
            self.driver = factory.create_driver()
            self.driver.maximize_window()

            # 登录操作
            username = self.username_entry.get()
            password = self.password_entry.get()
            if not all([username, password]):
                messagebox.showerror(" 错误", "请填写用户名和密码")
                return

            self.status_var.set(" 正在登录...")
            self._login(username, password)
            self.is_logged_in = True
            self.start_button.config(state='normal')
            self.status_var.set(" 浏览器初始化完成，已登录")

        except Exception as e:
            messagebox.showerror(" 错误", f"浏览器初始化失败: {str(e)}")
            self.status_var.set(f" 初始化失败: {str(e)}")
            if self.driver:
                self.driver.quit()
                self.driver = None

    def _login(self, username, password):
        """登录网站"""
        self.driver.get(LOGIN_URL)
        WebDriverWait(self.driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "el-input__inner"))
        )
        self.driver.find_element(By.XPATH, "//input[@placeholder='请输入邮箱前缀']").send_keys(username)
        self.driver.find_element(By.XPATH, "//input[@placeholder='请输入密码']").send_keys(password)
        self.driver.find_element(By.CSS_SELECTOR, "button[type='button']").click()
        sleep(1)

    def start_process(self):
        """开始下载日志流程"""
        if not self.driver or not self.is_logged_in:
            messagebox.showerror(" 错误", "请先初始化浏览器并登录")
            return

        device_id = self.sn_entry.get()
        if not device_id:
            messagebox.showerror(" 错误", "请输入设备SN")
            return
        if not is_sn_valid(device_id):
            messagebox.showerror(" 错误", "SN号不符合要求")
            return

        try:
            self.status_var.set(" 正在下载日志...")
            self.master.update()
            success = self._download_and_process(device_id, self.phone_entry.get())

            if success:
                messagebox.showinfo(" 成功", f"设备 {device_id} 日志下载并处理成功")
                self.status_var.set(" 操作完成")
            else:
                messagebox.showerror(" 错误", "日志下载或处理失败")
                self.status_var.set(" 操作失败")
        except Exception as e:
            messagebox.showerror(" 错误", f"发生错误: {str(e)}")
            self.status_var.set(f" 错误: {str(e)}")

    def _download_and_process(self, device_id, phone):
        """下载并处理日志文件"""
        self.driver.get("https://iot-admin.zuoyebang.cc/iot-admin-server/system/errorlog/table")

        # 输入设备信息
        self.driver.find_element(By.XPATH, '//*[@id="form-key-get"]/div/div/div/div[1]/input').send_keys(device_id)
        self.driver.find_element(By.XPATH, '//*[@id="form-search"]/div/ul/li[1]/input').send_keys(device_id)
        self.driver.find_element(By.XPATH, '//*[@id="form-key-get"]/div/div/div/div[3]/input').send_keys(phone)
        sleep(1)

        # 检查设备状态
        if not self._check_device_status():
            return False

        # 获取下载链接
        sleep(70)
        observer = PageDownloadObserver(self.driver)
        download_links = observer.wait_for_download_links()
        download_link = download_links[0]
        expected_filename = get_filname(download_link.get_attribute("href"))

        # 使用策略模式下载文件
        base_dir = Path("D:/实习生工作")
        try:
            self.download_context.state = DownloadState.AUTO_DOWNLOADING
            downloaded_file = self.download_context.strategy.download(
                download_link,
                expected_filename,
                base_dir
            )

            if downloaded_file:
                self.download_context.state = DownloadState.COMPLETED
                output_dir = base_dir / device_id
                self.status_var.set(" 下载完成，准备提取文件")
                return extract_and_copy(downloaded_file, base_dir, output_dir)
                #return extract(downloaded_file, base_dir, output_dir)
            else:
                self.download_context.state = DownloadState.FAILED
                return False
        except Exception as e:
            logging.error(f" 最终下载失败: {str(e)}")
            self.download_context.state = DownloadState.FAILED
            return False

    def _check_device_status(self):
        """检查设备状态"""
        click_count = 1
        while True:
            WebDriverWait(self.driver, 10).until(
                EC.element_to_be_clickable((By.XPATH, '/html/body/div[2]/div/button'))
            ).click()

            message = WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.XPATH, f'//*[@id="layui-layer{click_count}"]/div[2]'))
            ).text

            WebDriverWait(self.driver, 10).until(
                EC.element_to_be_clickable((By.XPATH, f'//*[@id="layui-layer{click_count}"]/div[3]/a'))
            ).click()

            click_count += 1
            if "设备不在线" in message:
                response = messagebox.askretrycancel(" 设备不在线", "设备不在线，请确认设备在线后重试")
                if not response:
                    return False
            else:
                return True

    def __del__(self):
        if self.driver:
            self.driver.quit()


def main():
    try:
        setup_logging()
        root = Tk()
        app = LoginApp(root)
        root.mainloop()
    except Exception as e:
        logging.error(f" 应用程序错误: {str(e)}")
        messagebox.showerror(" 错误", f"应用程序崩溃: {str(e)}")


if __name__ == "__main__":
    main()